SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%
2.7 KB · 52 lines tsx
Raw Blame History
1import Link from "next/link";2import type { Metadata } from "next";3import { Timeline } from "@/components/timeline";4import { Chip, Empty, PageHeader, Panel, Table, Td } from "@/components/ui";5import { api } from "@/lib/api";6import { relTime } from "@/lib/format";78export const dynamic = "force-dynamic";910export async function generateMetadata({ params }: { params: Promise<{ domain: string }> }): Promise<Metadata> {11  const { domain } = await params;12  return { title: `${domain} — domain timeline`, description: `Monitored URLs and detected changes on ${domain}.`, alternates: { canonical: `/domain/${domain}` } };13}1415export default async function DomainPage({ params, searchParams }: { params: Promise<{ domain: string }>; searchParams: Promise<{ cursor?: string }> }) {16  const { domain } = await params;17  const { cursor } = await searchParams;18  const d = await api.domain(domain, cursor);19  return (20    <>21      <PageHeader kicker="Domain" title={<span className="font-mono">{domain}</span>} description="Every monitored URL on this domain with its history, plus the event timeline." actions={d?.source_id ? <Link href={`/source/${d.source_id}`} className="rounded-md border border-line bg-panel px-2.5 py-1 text-[12px] hover:border-line-strong">Source page →</Link> : undefined} />22      <div className="grid gap-4 lg:grid-cols-[1fr_1fr]">23        <Panel title={`URLs · ${d?.urls.length ?? 0}`} dense>24          {d?.urls.length ? (25            <Table head={["URL", "Status", "Snapshots", "Changes", "Last seen"]}>26              {d.urls.map((u) => (27                <tr key={u.url}>28                  <Td><Link href={`/url?u=${encodeURIComponent(u.url)}`} className="block max-w-[26rem] truncate font-mono text-[11.5px] hover:underline">{u.url.replace(/^https?:\/\//, "")}</Link></Td>29                  <Td><Chip tone={u.status === "active" ? "ok" : u.status === "removed" ? "danger" : "warn"}>{u.status}</Chip></Td>30                  <Td mono>{u.snapshot_count}</Td>31                  <Td mono>{u.change_count}</Td>32                  <Td mono className="text-fg-subtle">{relTime(u.last_seen_at)}</Td>33                </tr>34              ))}35            </Table>36          ) : (37            <Empty>No URLs monitored on this domain yet.</Empty>38          )}39        </Panel>40        <Panel title="Timeline" dense>41          <Timeline events={d?.events ?? []} />42          {d?.nextCursor && (43            <div className="flex justify-end px-3 py-2 text-[12px]">44              <Link href={`/domain/${domain}?cursor=${encodeURIComponent(d.nextCursor)}`} className="rounded-md border border-line bg-panel-2 px-2.5 py-1 hover:border-line-strong">Older →</Link>45            </div>46          )}47        </Panel>48      </div>49    </>50  );51}52